﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// This class represents an array on the heap.
	/// </summary>
	internal sealed class CArray : CObject
	{
		/// <summary>
		/// If the array contains simple elements (numbers or pointers) the contents are here.
		/// </summary>
		private readonly SimpleMemoryValue[] m_arraycontents;

		/// <summary>
		/// The number of elements in the array (may differ from m_arraycontents.Length).
		/// </summary>
		private readonly int m_card;

		/// <summary>
		/// The template for the layout if the array is made of structs.
		/// </summary>
		private readonly CompositeValueEntry[] m_template;


		private CArray(int address, IRArrayType loctype, int size, SimpleMemoryValue[] contents, CompositeValueEntry[] template)
			: base(address, loctype, CompositeValue.MakeCompositeValue(loctype))
		{
			IRSignedIntegerType i32 = GlobalInterpreterState.TProg.TypeTable.Int32Type;
			IRBasicAccessPath cbap = GlobalInterpreterState.TProg.GetAccessPathForSingleField(loctype.CardField);
			CompositeValue.StorePassableNumericValTo(NumericValue.MakeSignedValueOfType(size, i32), cbap, this.m_objcontents);

			this.m_arraycontents = contents;
			this.m_card = size;
			this.m_template = template;
		}

		internal static CArray InterpAllocateArray(int address, IRArrayType loctype, NumericValue index)
		{
			int size = (int)index.ReadSignedValue();
			IRType oftype = loctype.ArrayContentsType;

			if(loctype.ArrayContentsType is IRPrimitiveNumericType || loctype.ArrayContentsType is IRPtrType || loctype.ArrayContentsType is IREnumType)
			{
				SimpleMemoryValue[] sma = new SimpleMemoryValue[size];
				for(int i = 0; i < sma.Length; ++i)
					sma[i] = SimpleMemoryValue.MakeInitialSimpleMemoryValueOfType(oftype);

				return new CArray(address, loctype, size, sma, null);
			}
			else
			{
				CompositeValueEntry[] singleuninit = CompositeValue.MakeCompositeValue((IRStructType)oftype);
				SimpleMemoryValue[] sma = new SimpleMemoryValue[size * singleuninit.Length];

				for(int i = 0; i < size; ++i)
					Array.Copy(singleuninit, 0, sma, i * singleuninit.Length, singleuninit.Length);

				return new CArray(address, loctype, size, sma, singleuninit);
			}
		}

		internal void InterpCopyArrayIntoThis_Clobber(CArray fromarray, int tstartpos, int fstartpos, int length)
		{
			IRType tt = ((IRArrayType)this.LocationType).ArrayContentsType;
			IRSignedIntegerType i32 = GlobalInterpreterState.TProg.TypeTable.Int32Type;

			if(tt is IRPrimitiveNumericType)
			{
				for(int i = 0; i < length; ++i)
				{
					NumericValue nnv = fromarray.ReadPassableNumericFromIndex(NumericValue.MakeSignedValueOfType(fstartpos + i, i32));
					this.StorePassableNumericValToIndex(nnv, NumericValue.MakeSignedValueOfType(tstartpos + i, i32));
				}
			}
			else if(tt is IRPtrType)
			{
				for(int i = 0; i < length; ++i)
				{
					PtrValue npv = fromarray.ReadPassablePtrValueFromIndex(NumericValue.MakeSignedValueOfType(fstartpos + i, i32));
					this.StorePassablePtrValToIndex(npv, NumericValue.MakeSignedValueOfType(tstartpos + i, i32));
				}
			}
			else
			{
				for(int i = 0; i < length; ++i)
				{
					CompositeValueEntry[] ncve = fromarray.ReadCompositeFromIndex(NumericValue.MakeSignedValueOfType(fstartpos + i, i32));
					this.StoreCompositeToIndex(ncve, NumericValue.MakeSignedValueOfType(tstartpos + i, i32));
				}
			}
		}

		private bool IndexBoundsOk(int index)
		{
			TracingSystem.LogUseFromLocation(this.Address, this.GenArrayLenthPath(), this.LocationType);
			return (0 <= index) & (index < this.m_card); 
		}

		private IRBasicAccessPath GenArrayLenthPath()
		{ return GlobalInterpreterState.TProg.GetAccessPathForSingleField(((IRArrayType)this.LocationType).CardField); }

		private IRBasicAccessPath GenArrayContentsPath()
		{ return GlobalInterpreterState.TProg.GetAccessPathForSingleField(((IRArrayType)this.LocationType).ContentsField); }

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder(this.LocationType.BaseTypeName + "[");

			if(this.m_template == null)
			{
				IRType oftype = ((IRArrayType)this.LocationType).ArrayContentsType;
				for(int i = 0; i < this.m_arraycontents.Length; ++i)
					sb.Append(String.Format("{0}{1}", (i != 0 ? ", " : ""), this.m_arraycontents[i].ToString()));
			}
			else
			{
				CompositeValueEntry[] cvee = new CompositeValueEntry[this.m_template.Length];

				for(int i = 0; i < this.m_card; ++i)
				{
					int ipos = i * this.m_template.Length;
					for(int j = 0; j < this.m_template.Length; ++j)
						cvee[j] = new CompositeValueEntry(this.m_template[j].Offset, this.m_arraycontents[ipos + j]);

					sb.Append(String.Format("{0}{1}", (i != 0 ? ", " : ""), CompositeValue.Stringify(cvee)));
				}
			}

			sb.Append("]");
			return sb.ToString();
		}

		internal override string DebuggerString()
		{
			return String.Format("Array of {0}", this.LocationType.BaseTypeName);
		}

		internal override void SerializeForHeapDump(System.IO.TextWriter dumpstr)
		{
			string ohs = String.Format("LFlag=Array LAddr={0} LType={1} LValue=", this.Address, this.LocationType.TypeID);
			dumpstr.Write(ohs);
			
			dumpstr.Write("ACont=[");
			for(int i = 0; i < this.m_arraycontents.Length; ++i)
			{
				if(i != 0)
					dumpstr.Write(",");
				this.m_arraycontents[i].SerializeForHeapDump(dumpstr);
			}
			dumpstr.Write("]");
		}

		internal override void SerializeForDGMLDisplay(System.Xml.XmlWriter nodexw, System.Xml.XmlWriter edgexw, string lname)
		{
			nodexw.WriteStartElement("Node");
			nodexw.WriteAttributeString("Id", this.Address.ToString());
			nodexw.WriteAttributeString("Label", this.LocationType.BaseTypeName);
			nodexw.WriteAttributeString("Background", "grey");
			nodexw.WriteEndElement();

			IRType act = ((IRArrayType)this.LocationType).ArrayContentsType;
			if(act is IRStructType)
			{
				for(int i = 0; i < this.m_card; ++i)
				{
					for(int j = 0; j < this.m_template.Length; ++j)
					{
						IRType altype = this.m_template[j].Offset.PathLast.FieldType;
						if(altype is IRPtrType)
						{
							PtrValue pv = this.m_arraycontents[i * this.m_template.Length + j].ConvertToPassablePtrValue();
							if(!pv.IsNullPtr)
							{
								edgexw.WriteStartElement("Link");
								edgexw.WriteAttributeString("Source", this.Address.ToString());
								edgexw.WriteAttributeString("Target", pv.TargetAddress.ToString());
								edgexw.WriteAttributeString("Label", String.Format("[{0}].{1}", i, this.m_template[j].Offset.FieldOnlyDisplayString()));
								edgexw.WriteEndElement();
							}
						}
					}
				}
			}
			else if(act is IRPtrType)
			{
				for(int i = 0; i < this.m_arraycontents.Length; ++i)
				{
					PtrValue pv = this.m_arraycontents[i].ConvertToPassablePtrValue();
					if(!pv.IsNullPtr)
					{
						edgexw.WriteStartElement("Link");
						edgexw.WriteAttributeString("Source", this.Address.ToString());
						edgexw.WriteAttributeString("Target", pv.TargetAddress.ToString());
						edgexw.WriteAttributeString("Label", String.Format("[{0}]", i));
						edgexw.WriteEndElement();
					}
				}
			}
			else
			{
				;
			}
		}

		internal static CArray ArrayParseLocation(Dictionary<string, string> pkvp)
		{
			Debug.Assert(pkvp["LFlag"].Equals("Array"));

			int addr = Int32.Parse(pkvp["LAddr"]);

			int typeid = Int32.Parse(pkvp["LType"]);
			IRArrayType ltype = (IRArrayType)GlobalInterpreterState.TProg.TypeTable.LookupTypeForID(typeid);

			int subdiv;
			CompositeValueEntry[] template;
			if(ltype.ArrayContentsType is IRStructType)
			{
				subdiv = ltype.ArrayContentsType.TypeDescription.AllTerminalAccessPaths.Count;
				template = CompositeValue.MakeCompositeValue((IRStructType)ltype.ArrayContentsType);
			}
			else
			{
				subdiv = 1;
				template = null;
			}

			string ars = pkvp["ACont"];
			string[] acs = ars.Substring(1, ars.Length - 2).Split(new char[] { ',' });
			SimpleMemoryValue[] smv = new SimpleMemoryValue[acs.Length];

			if(ltype.ArrayContentsType is IRStructType)
			{
				for(int i = 0; i < acs.Length / subdiv; ++i)
				{
					for(int j = 0; j < subdiv; ++j)
						smv[i * subdiv + j] = SimpleMemoryValue.ParseValue(template[j].Offset.PathLast.FieldType, acs[i * subdiv + j]);
				}
			}
			else
			{
				for(int i = 0; i < acs.Length; ++i)
					smv[i] = SimpleMemoryValue.ParseValue(ltype.ArrayContentsType, acs[i]);
			}

			return new CArray(addr, ltype, smv.Length / subdiv, smv, template);
		}

		//////////////////////////////////////////////////////
		//Array specific accesses

		internal NumericValue ReadArrayLength()
		{
			return this.ReadPassableNumericFromField(((IRArrayType)this.LocationType).CardField);
		}

		internal PtrValue ReadPassablePtrValueFromIndex(NumericValue index)
		{
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRPtrType);

			int pos = (int)index.ReadSignedValue();
			if(!this.IndexBoundsOk(pos))
				throw new InterpRuntimeException("Array Out of Bounds");

			TracingSystem.LogUseFromLocation(this.Address, this.GenArrayContentsPath(), this.LocationType);

			return this.m_arraycontents[pos].ConvertToPassablePtrValue();
		}

		internal void StorePassablePtrValToIndex(PtrValue pv, NumericValue index)
		{
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRPtrType);
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType == pv.ValType);

			int pos = (int)index.ReadSignedValue();
			if(!this.IndexBoundsOk(pos))
				throw new InterpRuntimeException("Array Out of Bounds");

			TracingSystem.LogOldPointer_InArray(this.Address, pos, this.m_arraycontents[pos].ConvertToPassablePtrValue());
			TracingSystem.LogModToLocation(this.Address, this.GenArrayContentsPath(), this.LocationType);

			this.m_arraycontents[pos] = SimpleMemoryValue.ConvertPtrValToSimpleMemoryVersion(pv);
		}

		internal NumericValue ReadPassableNumericFromIndex(NumericValue index)
		{
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRPrimitiveIntegerType || ((IRArrayType)this.LocationType).ArrayContentsType is IREnumType);

			int pos = (int)index.ReadSignedValue();
			if(!this.IndexBoundsOk(pos))
				throw new InterpRuntimeException("Array Out of Bounds");

			TracingSystem.LogUseFromLocation(this.Address, this.GenArrayContentsPath(), this.LocationType);

			return this.m_arraycontents[pos].ConvertToPassableNumericValue();
		}

		internal void StorePassableNumericValToIndex(NumericValue nv, NumericValue index)
		{
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRPrimitiveIntegerType || ((IRArrayType)this.LocationType).ArrayContentsType is IREnumType);
			Debug.Assert((((IRArrayType)this.LocationType).ArrayContentsType is IRPrimitiveIntegerType && ((IRArrayType)this.LocationType).ArrayContentsType == nv.ValType) || (((IRArrayType)this.LocationType).ArrayContentsType is IREnumType && ((IREnumType)((IRArrayType)this.LocationType).ArrayContentsType).UnerlyingNumericType == nv.ValType));

			int pos = (int)index.ReadSignedValue();
			if(!this.IndexBoundsOk(pos))
				throw new InterpRuntimeException("Array Out of Bounds");

			TracingSystem.LogOldNumeric_InArray(this.Address, pos, this.m_arraycontents[pos].ConvertToPassableNumericValue());
			TracingSystem.LogModToLocation(this.Address, this.GenArrayContentsPath(), this.LocationType);

			this.m_arraycontents[pos] = SimpleMemoryValue.ConvertNumericValToSimpleMemoryVersion(nv);
		}

		internal CompositeValueEntry[] ReadCompositeFromIndex(NumericValue index)
		{
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRStructType);

			int pos = (int)index.ReadSignedValue();
			if(!this.IndexBoundsOk(pos))
				throw new InterpRuntimeException("Array Out of Bounds");

			TracingSystem.LogUseFromLocation(this.Address, this.GenArrayContentsPath(), this.LocationType);

			CompositeValueEntry[] cvee = new CompositeValueEntry[this.m_template.Length];
			int ipos = pos * this.m_template.Length;
			for(int i = 0; i < this.m_template.Length; ++i)
			{
				SimpleMemoryValue smv = this.m_arraycontents[ipos + i];
				cvee[i] = new CompositeValueEntry(this.m_template[i].Offset, smv);
			}

			return cvee;
		}

		internal void StoreCompositeToIndex(CompositeValueEntry[] nv, NumericValue index)
		{
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRStructType);

			int pos = (int)index.ReadSignedValue();
			if(!this.IndexBoundsOk(pos))
				throw new InterpRuntimeException("Array Out of Bounds");

			int ipos = pos * this.m_template.Length;

			TracingSystem.LogOldComposite_InArray(this.Address, ipos, this.m_template.Length, this.m_arraycontents);
			TracingSystem.LogModToLocation(this.Address, this.GenArrayContentsPath(), this.LocationType);

			for(int i = 0; i < this.m_template.Length; ++i)
				this.m_arraycontents[ipos + i] = nv[i].Value;
		}

		//////////////////////////////////////////////////////
		//Overidden accesses and operations

		internal override IEnumerable<int> EnumerateTrgtAddresses()
		{
			if(((IRArrayType)this.LocationType).ArrayContentsType is IRPtrType)
			{
				for(int i = 0; i < this.m_arraycontents.Length; ++i)
				{
					PtrValue pv = this.m_arraycontents[i].ConvertToPassablePtrValue();
					yield return pv.TargetAddress;
				}
			}

			if(((IRArrayType)this.LocationType).ArrayContentsType is IRStructType)
			{
				for(int i = 0; i < this.m_card; ++i)
				{
					int ipos = i * this.m_template.Length;
					for(int j = 0; j < this.m_template.Length; ++j)
					{
						if(this.m_template[j].Offset.PathLast.FieldType is IRPtrType)
							yield return this.m_arraycontents[ipos + j].ConvertToPassablePtrValue().TargetAddress;
					}
				}
			}
		}

		internal override PtrValue ReadPassablePtrValueFrom(ReferenceValue rf)
		{
			Debug.Assert(!rf.TargetOffset.IsEmptyAccessPath);
			Debug.Assert(rf.TargetOffset.PathLast.FieldType is IRPtrType);

			if(!this.IndexBoundsOk(rf.OptArrayIndex))
				throw new InterpRuntimeException("Array Out of Bounds");

			IRSignedIntegerType i32 = GlobalInterpreterState.TProg.TypeTable.Int32Type;

			if(rf.TargetOffset.Path.Length == 1)
				return this.ReadPassablePtrValueFromIndex(NumericValue.MakeSignedValueOfType(rf.OptArrayIndex, i32));
			else
			{
				TracingSystem.LogUseFromLocation(this.Address, rf.TargetOffset, this.LocationType);

				int ipos = rf.OptArrayIndex * this.m_template.Length;
				IRBasicAccessPath tp = rf.TargetOffset.Tail;
				int jpos = Array.FindIndex(this.m_template, (CompositeValueEntry cvee) => cvee.Offset == tp);

				return this.m_arraycontents[ipos + jpos].ConvertToPassablePtrValue();
			}
		}

		internal override void StorePassablePtrValTo(PtrValue pv, ReferenceValue rf)
		{
			Debug.Assert(!rf.TargetOffset.IsEmptyAccessPath);
			Debug.Assert(rf.TargetOffset.PathLast.FieldType is IRPtrType);
			Debug.Assert(rf.TargetOffset.PathLast.FieldType == pv.ValType);

			if(!this.IndexBoundsOk(rf.OptArrayIndex))
				throw new InterpRuntimeException("Array Out of Bounds");

			IRSignedIntegerType i32 = GlobalInterpreterState.TProg.TypeTable.Int32Type;

			if(rf.TargetOffset.Path.Length == 1)
			{
				TracingSystem.LogOldPointer_InArray(this.Address, rf.OptArrayIndex, this.m_arraycontents[rf.OptArrayIndex].ConvertToPassablePtrValue());
				TracingSystem.LogModToLocation(this.Address, rf.TargetOffset, this.LocationType);

				this.StorePassablePtrValToIndex(pv, NumericValue.MakeSignedValueOfType(rf.OptArrayIndex, i32));
			}
			else
			{
				int ipos = rf.OptArrayIndex * this.m_template.Length;
				IRBasicAccessPath tp = rf.TargetOffset.Tail;
				int jpos = Array.FindIndex(this.m_template, (CompositeValueEntry cvee) => cvee.Offset == tp);

				TracingSystem.LogOldPointer_InArray(this.Address, ipos + jpos, this.m_arraycontents[ipos + jpos].ConvertToPassablePtrValue());
				TracingSystem.LogModToLocation(this.Address, rf.TargetOffset, this.LocationType);

				this.m_arraycontents[ipos + jpos] = SimpleMemoryValue.ConvertPtrValToSimpleMemoryVersion(pv);
			}
		}

		internal override NumericValue ReadPassableNumericFrom(ReferenceValue rf)
		{
			Debug.Assert(!rf.TargetOffset.IsEmptyAccessPath);
			Debug.Assert(rf.TargetOffset.PathLast.FieldType is IRPrimitiveNumericType || rf.TargetOffset.PathLast.FieldType is IREnumType);

			if(rf.TargetOffset == this.GenArrayLenthPath())
				return this.ReadPassableNumericFromField(((IRArrayType)this.LocationType).CardField);
			else
			{
				if(!this.IndexBoundsOk(rf.OptArrayIndex))
					throw new InterpRuntimeException("Array Out of Bounds");

				IRSignedIntegerType i32 = GlobalInterpreterState.TProg.TypeTable.Int32Type;
			
				if(rf.TargetOffset.Path.Length == 1)
					return this.ReadPassableNumericFromIndex(NumericValue.MakeSignedValueOfType(rf.OptArrayIndex, i32));
				else
				{
					TracingSystem.LogUseFromLocation(this.Address, rf.TargetOffset, this.LocationType);

					int ipos = rf.OptArrayIndex * this.m_template.Length;
					IRBasicAccessPath tp = rf.TargetOffset.Tail;
					int jpos = Array.FindIndex(this.m_template, (CompositeValueEntry cvee) => cvee.Offset == tp);

					return this.m_arraycontents[ipos + jpos].ConvertToPassableNumericValue();
				}
			}
		}

		internal override void StorePassableNumericValTo(NumericValue nv, ReferenceValue rf)
		{
			Debug.Assert(!rf.TargetOffset.IsEmptyAccessPath);
			Debug.Assert(rf.TargetOffset.PathLast.FieldType is IRPrimitiveNumericType || rf.TargetOffset.PathLast.FieldType is IREnumType);
			Debug.Assert((rf.TargetOffset.PathLast.FieldType is IRPrimitiveNumericType && rf.TargetOffset.PathLast.FieldType == nv.ValType) || (rf.TargetOffset.PathLast.FieldType is IREnumType && ((IREnumType)rf.TargetOffset.PathLast.FieldType).UnerlyingNumericType == nv.ValType));

			if(!this.IndexBoundsOk(rf.OptArrayIndex))
				throw new InterpRuntimeException("Array Out of Bounds");

			IRSignedIntegerType i32 = GlobalInterpreterState.TProg.TypeTable.Int32Type;

			if(rf.TargetOffset.Path.Length == 1)
			{
				TracingSystem.LogOldNumeric_InArray(this.Address, rf.OptArrayIndex, this.m_arraycontents[rf.OptArrayIndex].ConvertToPassableNumericValue());
				TracingSystem.LogModToLocation(this.Address, rf.TargetOffset, this.LocationType);

				this.StorePassableNumericValToIndex(nv, NumericValue.MakeSignedValueOfType(rf.OptArrayIndex, i32));
			}
			else
			{
				int ipos = rf.OptArrayIndex * this.m_template.Length;
				IRBasicAccessPath tp = rf.TargetOffset.Tail;
				int jpos = Array.FindIndex(this.m_template, (CompositeValueEntry cvee) => cvee.Offset == tp);

				TracingSystem.LogOldNumeric_InArray(this.Address, ipos + jpos, this.m_arraycontents[ipos + jpos].ConvertToPassableNumericValue());
				TracingSystem.LogModToLocation(this.Address, rf.TargetOffset, this.LocationType);

				this.m_arraycontents[ipos + jpos] = SimpleMemoryValue.ConvertNumericValToSimpleMemoryVersion(nv);
			}
		}

		internal override CompositeValueEntry[] ReadCompositeFrom(ReferenceValue rf)
		{
			Debug.Assert(!rf.TargetOffset.IsEmptyAccessPath);
			Debug.Assert(rf.TargetOffset.PathLast.FieldType is IRStructType);
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRStructType);

			if(!this.IndexBoundsOk(rf.OptArrayIndex))
				throw new InterpRuntimeException("Array Out of Bounds");

			TracingSystem.LogUseFromLocation(this.Address, rf.TargetOffset, this.LocationType);

			CompositeValueEntry[] cvee = new CompositeValueEntry[this.m_template.Length];
			int ipos = rf.OptArrayIndex * this.m_template.Length;
			for(int i = 0; i < this.m_template.Length; ++i)
				cvee[i] = new CompositeValueEntry(this.m_template[i].Offset, this.m_arraycontents[ipos + i]);

			IRBasicAccessPath tp = rf.TargetOffset.Tail;
			return CompositeValue.ReadCompositeFrom(tp, cvee, ((IRArrayType)this.LocationType).ArrayContentsType);
		}

		internal override void StoreCompositeTo(CompositeValueEntry[] cv, ReferenceValue rf)
		{
			Debug.Assert(!rf.TargetOffset.IsEmptyAccessPath);
			Debug.Assert(rf.TargetOffset.PathLast.FieldType is IRStructType);
			Debug.Assert(((IRArrayType)this.LocationType).ArrayContentsType is IRStructType);

			if(!this.IndexBoundsOk(rf.OptArrayIndex))
				throw new InterpRuntimeException("Array Out of Bounds");

			int ipos = rf.OptArrayIndex * this.m_template.Length;

			TracingSystem.LogOldComposite_InArray(this.Address, ipos, cv.Length, this.m_arraycontents);
			TracingSystem.LogModToLocation(this.Address, rf.TargetOffset, this.LocationType);

			CompositeValueEntry[] cvee = new CompositeValueEntry[this.m_template.Length];
			for(int i = 0; i < this.m_template.Length; ++i)
				cvee[i] = new CompositeValueEntry(this.m_template[i].Offset, this.m_arraycontents[ipos + i]);

			IRBasicAccessPath tp = rf.TargetOffset.Tail;
			CompositeValue.StoreCompositeTo(cv, tp, cvee, ((IRArrayType)this.LocationType).ArrayContentsType);

			for(int i = 0; i < cvee.Length; ++i)
				this.m_arraycontents[ipos + i] = cvee[i].Value;
		}
	}
}
